using System.Runtime.CompilerServices;
using System.Text;

//
// * Copyright (c) 2002-2009 "Neo Technology,"
// *     Network Engine for Objects in Lund AB [http://neotechnology.com]
// *
// * This file is part of Neo4j.
// * 
// * Neo4j is free software: you can redistribute it and/or modify
// * it under the terms of the GNU Affero General Public License as
// * published by the Free Software Foundation, either version 3 of the
// * License, or (at your option) any later version.
// * 
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// * GNU Affero General Public License for more details.
// * 
// * You should have received a copy of the GNU Affero General Public License
// * along with this program. If not, see <http://www.gnu.org/licenses/>.
// 
namespace org.neo4j.kernel.impl.transaction
{


	using Xid = javax.transaction.xa.Xid;

	public class XidImpl : Xid
	{
	// Neo4j ('N' 'E'  'O') format identifier
		private const int FORMAT_ID = 0x4E454E31;

		private static readonly sbyte[] INSTANCE_ID = new sbyte[] { 'N', 'E', 'O', 'K', 'E', 'R', 'N', 'L', '\0' };

		private static long nextSequenceId = 0;

	// next sequence number for global tx id
		[MethodImpl(MethodImplOptions.Synchronized)]
		private static long getNextSequenceId()
		{
			return nextSequenceId++;
		}

	// INSTANCE_ID + millitime(long) + seqnumber(long)
		private readonly sbyte[] globalId;
	// branchId assumes Xid.MAXBQUALSIZE >= 4
		private readonly sbyte[] branchId;
		private readonly int formatId;


	// resourceId.length = 4, unique for each XAResource
		internal static sbyte[] getNewGlobalId()
		{
		// create new global id ( [INSTANCE_ID][time][sequence] )
			sbyte[] globalId = new sbyte[INSTANCE_ID.Length + 16];
			System.Array.Copy(INSTANCE_ID, 0, globalId, 0, INSTANCE_ID.Length);
			ByteBuffer byteBuf = ByteBuffer.wrap(globalId);
			byteBuf.position(INSTANCE_ID.Length);
			long time = System.currentTimeMillis();
			long sequence = getNextSequenceId();
			byteBuf.putLong(time).putLong(sequence);
			return globalId;
		}

		internal static bool isThisTm(sbyte[] globalId)
		{
			if (globalId.Length < INSTANCE_ID.Length)
			{
				return false;
			}
			for (int i = 0; i < INSTANCE_ID.Length; i++)
			{
				if (globalId[i] != INSTANCE_ID[i])
				{
					return false;
				}
			}
			return true;
		}

		public XidImpl(sbyte[] globalId, sbyte[] resourceId) : this(globalId, resourceId, FORMAT_ID)
		{
		}

	// create xid for transaction with more than one XAResource enlisted
		public XidImpl(sbyte[] globalId, sbyte[] resourceId, int formatId)
		{
			if (globalId.Length > Xid.MAXGTRIDSIZE)
			{
				throw new IllegalArgumentException("GlobalId length to long, " + globalId.Length);
			}
			if (resourceId.Length > Xid.MAXBQUALSIZE)
			{
				throw new IllegalArgumentException("BranchId (resource id) to long, " + resourceId.Length);
			}
			this.globalId = globalId;
			this.branchId = resourceId;
			this.formatId = formatId;
		}

		public virtual sbyte[] getGlobalTransactionId()
		{
			return globalId.clone();
		}

		public virtual sbyte[] getBranchQualifier()
		{
			return branchId.clone();
		}

		public virtual int getFormatId()
		{
			return FORMAT_ID;
		}

		public override bool Equals(object o)
		{
			if (!(o is Xid))
			{
				return false;
			}
			sbyte[] otherGlobalId = ((Xid) o).getGlobalTransactionId();
			sbyte[] otherBranchId = ((Xid) o).getBranchQualifier();

			if (globalId.Length != otherGlobalId.Length || branchId.Length != otherBranchId.Length)
			{
				return false;
			}

			for (int i = 0; i < globalId.Length; i++)
			{
				if (globalId[i] != otherGlobalId[i])
				{
					return false;
				}
			}
			for (int i = 0; i < branchId.Length; i++)
			{
				if (branchId[i] != otherBranchId[i])
				{
					return false;
				}
			}
			return true;
		}

		private volatile int hashCode = 0;

		public override int GetHashCode()
		{
			if (hashCode == 0)
			{
				int calcHash = 0;
				for (int i = 0; i < 3 && i < globalId.Length; i++)
				{
					calcHash += globalId[globalId.Length - i - 1] << i * 8;
				}
				if (branchId.Length > 0)
				{
					calcHash += branchId[0] << 3 * 8;
				}
				hashCode = 3217 * calcHash;
			}
			return hashCode;
		}

		public override string ToString()
		{
			StringBuilder buf = new StringBuilder("GlobalId[");
			if (globalId.Length == (INSTANCE_ID.Length + 8 + 8))
			{
				for (int i = 0; i < INSTANCE_ID.Length - 1; i++)
				{
					buf.Append((char) globalId[i]);
				}
				ByteBuffer byteBuf = ByteBuffer.wrap(globalId);
				byteBuf.position(INSTANCE_ID.Length);
				long time = byteBuf.getLong();
				long sequence = byteBuf.getLong();
				buf.Append('|');
				buf.Append(time);
				buf.Append('|');
				buf.Append(sequence);
			}
			else
			{
				buf.Append("UNKNOWN_ID");
			}
			buf.Append("], BranchId[ ");
			for (int i = 0; i < branchId.Length; i++)
			{
				buf.Append(branchId[i] + " ");
			}
			buf.Append("]");
			return buf.ToString();
		}
	}
}